using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Linq;


namespace GuavaData
{
    public class HMerge : Transform
    {
        List<string> toMerge;
        string resName;
        string newColumn;
        //Scheme merged;
        Dictionary<string, Dictionary<string, Domain.Domain>> inputTables;  //keep table name, columns, and Domains of each input table
        List<string> merKeys; // keep key of merged table
        Dictionary<string, Domain.Domain> mergedColDomains;  // keep columns and Domains of merged table
        List<Operator> aeList; // keep add Element operators of the transaction 
        List<Operator> reList; // keep rename Element operators of the transaction
        List<Operator> deList; // keep drop Element operators of the transaction
        /// <summary>
        /// Horizontal merge operation for mutiple tables in a schema
        /// </summary>
        /// <param name="inName">Name of the new merged table</param>
        /// <param name="tables">List of the tables that will be merged together</param>
        /// <param name="newColumn">Name of the column that record table name of each row</param>
        public HMerge(List<string> tables, string inName, string newColumn)
            : base()
        {
            toMerge = tables;
            this.resName = inName;
            this.newColumn = newColumn;

            inputTables = null;
            mergedColDomains = null;
            merKeys = new List<string>();
            aeList = new List<Operator>();
            reList = new List<Operator>();
            deList = new List<Operator>();
        }
        /// <summary>
        /// To add a new table needs to check that the merged table have already exist or not. 
        /// If not exist, create the merged table
        /// If exist, change add table operator to add element and add column operator instead.
        /// </summary>
        /// <param name="at"></param>
        /// <param name="c"></param>
        internal override void Expand(AddTable at, Command c)
        {
            // If the table doesn't match, punt
            if (!toMerge.Contains(at.Table))
                return;
             // If the merge table already exists
            if (mergedColDomains != null)
            {
                // check that at.Keys is the same as the merged table keys or not 
                bool flags = true;
                foreach (string key in at.Keys)
                {
                    if (!this.merKeys.Contains(key))
                    {
                        flags = false;
                        break;
                    }
                }
                if (flags)
                {
                    List<Command> colList = new List<Command>();
                    AddElement addElem = new AddElement(resName, newColumn, at.Table);
                    colList.Add(new Command(addElem));
                    Dictionary<string, Domain.Domain> newTabIn = new Dictionary<string, GuavaData.Domain.Domain>();
                    // If the coulmn in the add table parameter exist in the merged table, then their domains have to be the same.  
                    for (int i = 0; i < at.Columns.Count; i++)
                    {// The same column name have to have the same domain also
                        if (mergedColDomains.ContainsKey(at.Columns[i]))
                        {
                             if (!at.Domains[i].Equals(mergedColDomains[at.Columns[i]]))
                            {
                                throw new Exception("Domain " + at.Domains[i] + " of column " + at.Columns[i] + " NOT match with the existing Domain.\r\n" + //<-This means nothing...
                                 "i.e. When two or more tables are horizontally merged, if there are columns with the same name, they must have the same domain.");
                            }

                        }
                        else// if the column does not exist in the merged table, then add column to the merged table
                        {
                            colList.Add(new Command(new AddColumn(resName, at.Columns[i], at.Domains[i])));
                            mergedColDomains[at.Columns[i]] = at.Domains[i]; // add the new column in the merged table
                        }
                                               
                        newTabIn[at.Columns[i]] = at.Domains[i];
                    }
                    
                    // Alter the domain of newColumn

                    /*
                    Domain.Enumerated e = (Domain.Enumerated)mergedColDomains[newColumn];
                    //Domain.Enumerated eDom = new GuavaData.Domain.Enumerated(e.Items);

                    e.Items.Add(at.Table);
                    //merColumns[newColumn] = eDom;
                    newTabIn[newColumn] = e;
                    */
                    inputTables[at.Table] = newTabIn;
                    c.NotifyChange(colList.ToArray());
                }
                else
                {
                    throw new Exception("Key " + at.Keys + " of the new table NOT match with the merged table. \r\n"
                        +"The keys of the add table command for " + at.Table + "do not match those in the HMerge.");
                }
            }
            else// If the merge table have not already existed
            {
                merKeys = at.Keys;
                mergedColDomains = new Dictionary<string, GuavaData.Domain.Domain>();
                for (int j = 0; j < at.Columns.Count; j++)
                {
                    mergedColDomains[at.Columns[j]] = at.Domains[j];
                }
                // keep the input table in the memory
                inputTables = new Dictionary<string, Dictionary<string, GuavaData.Domain.Domain>>();
                inputTables[at.Table] = mergedColDomains;
                // add newColumn in the list of column of merged table in the memory
                Domain.Enumerated enew = new GuavaData.Domain.Enumerated();
                foreach(String str in toMerge)
                    enew.Items.Add(str);//str used to be at.table
                mergedColDomains[newColumn] = enew.Clone;

                at.Keys.Add(this.newColumn);
                at.Columns.Add(newColumn);
                at.Domains.Add(enew);
                AddTable newTab = new AddTable(resName, at.Columns, at.Domains, at.Keys);
                c.NotifyChange(new Command(newTab));
            }
        }
        /// <summary>
        /// Add a new column to the table. There are three cases.
        /// Case 1: If the column exists in the merged table and the domain is the same as the existed domain,
        ///         then drops the statement and corrects the internal representation. 
        /// Case 2: If the column exists in the merged table and the domain is not the same as the existed domain,
        ///         then throws error.
        /// Case 3: If the column is not in the merged table then add the new column to merged table.
        /// 
        /// </summary>
        /// <param name="ac"></param>
        /// <param name="c"></param>
        internal override void Expand(AddColumn ac, Command c)
        {
            // If the table doesn't match, punt
            if (!toMerge.Contains(ac.Table))
                return;

            // If the column already exists and the domian is not equal to the existed one then throws error
            if (mergedColDomains.ContainsKey(ac.Column))
            {
                //case1
                if (ac.Domain.Equals(mergedColDomains[ac.Column]))
                {
                    Dictionary<string, Domain.Domain> colDic = inputTables[ac.Table];
                    colDic[ac.Column] = ac.Domain;// update internal representation
                    c.NotifyChange();
                }
                else // case 2
                    throw new Exception("Domain " + ac.Domain + " of Column " + ac.Column + " does not match with the existing domain.");
            }
            else// case 3
            {
                // Add the column to the internal represenation
                mergedColDomains[ac.Column] = ac.Domain;
                Dictionary<string, Domain.Domain> colDic = inputTables[ac.Table];
                colDic[ac.Column] = ac.Domain;
                AddColumn addC = new AddColumn(resName, ac.Column, ac.Domain);
                c.NotifyChange(new Command(addC));
            }
        }
        /// <summary>
        /// To add an Element to a coulomn needs to check that there are all add element functions  
        /// for every table which has the same column in the same transaction or not. If not throw an error. 
        /// </summary>
        /// <param name="ae"></param>
        /// <param name="c"></param>
        internal override void Expand(AddElement ae, Command c)
        {
            // If the table doesn't match, punt
            if (!toMerge.Contains(ae.Table))
                return;
            // Check that there is a add element operator of the same element and column before or not.
            // If yes, the operator have already operated.
            if (aeList.Contains(ae))
            {
                c.NotifyChange();
            }
            else
            {
               // Dictionary<string, Dictionary<string, Domain.Domain>> tempTabs = inputTables;
                Dictionary<string, Dictionary<string, Domain.Domain>> tempTabs = new Dictionary<string, Dictionary<string, GuavaData.Domain.Domain>>(inputTables);
                tempTabs.Remove(ae.Table);
                // which table that have the same column name as the ae.Column
                List<string> tbList = new List<string>();
                foreach (KeyValuePair<string, Dictionary<string, Domain.Domain>> p in tempTabs)
                {
                    Dictionary<string, Domain.Domain> colL = p.Value;
                    if (colL.ContainsKey(ae.Column))
                    {
                        tbList.Add(p.Key);

                    }
                }
                List<Command> comL = c.container.Contents;
                List<Operator> tempOp = new List<Operator>();// keep the operator that is valid from the transaction
                if (tbList.Count > 0)
                {
                    bool flags = false;
                    // Each table in the list has to be a parameter in each AddElement of the transaction, if not found throw an error.
                    for (int i = 0; i < tbList.Count; i++)
                    {
                        flags = false;
                        foreach (Command a in comL)
                        {
                            if ((a.root is AddElement) && ((a.root as AddElement).Table.Equals(tbList[i]))
                                && ((a.root as AddElement).Column.Equals(ae.Column)) && ((a.root as AddElement).Element.Equals(ae.Element)))
                            {
                                    tempOp.Add(a.root);
                                    flags = true;
                                    break;               
                            }
                        }
                        if (!flags)
                        {
                            throw new Exception("the Element " + ae.Element + " needs to be added to all the same Column " + ae.Column + " .");
                        }
                    }
                    this.aeList.AddRange(tempOp);// combine the temp operator list to the add element list in memory
                    //alter domain in the column
                    Domain.Enumerated e = mergedColDomains[ae.Column] as Domain.Enumerated;
                    Domain.Enumerated enew = new GuavaData.Domain.Enumerated(e.Items);
                    enew.Items.Add(ae.Element);
                    mergedColDomains[ae.Column] = enew;
                    Dictionary<string, Domain.Domain> d = inputTables[ae.Table];
                    d[ae.Column] = enew;
                    if (tbList.Count > 0)// update domain of all table which have the same column as ae.column
                    {
                        for (int j = 0; j < tbList.Count; j++)
                        {
                            Dictionary<string, Domain.Domain> dt = inputTables[tbList[j]];
                            dt[ae.Column] = enew;
                        }
                    }
                    ae.Table = resName;
                }
            }
        }
        /// <summary>
        /// Change the name of the table to a new name. 
        /// First, check that the new name is unique,then changes the old name to new name in the column cout. 
        /// Then RenameElement the old name to the new name in the column of the merged table
        /// </summary>
        /// <param name="atr"></param>
        /// <param name="c"></param>
        internal override void Expand(AlterTableRename atr, Command c)
        {
            // If the table doesn't match, punt
            if (!toMerge.Contains(atr.OldName))
                return;
            if (!toMerge.Contains(atr.NewName))// atr.NewName is not included in the input table list
            {
                toMerge.Remove(atr.OldName);
                toMerge.Add(atr.NewName);
                
            }
            
            // update the input table in the memory 
            Dictionary<string, Domain.Domain> colDic = inputTables[atr.OldName];
            inputTables.Remove(atr.OldName);
            inputTables[atr.NewName] = colDic;
            AlterElementRename reTab = new AlterElementRename(resName, newColumn, atr.OldName, atr.NewName);
            c.NotifyChange(new Command(reTab));
        }
        /// <summary>
        /// To change the column name in the table. There are four cases:
        /// Case 1: If the old column name is unique but the new one exists, 
        ///         move all of the data from the old column to the new one with an update statement and drop the old column. 
        /// Case 2: If there are no other tables with either the new or the old column, 
        ///         then simply rename the column, since there will be no other side effects.
        /// Case 3: If both the old and new column names exists, 
        ///         then move the relevant data over with an update statement and set the old values to null.
        /// Case 4: If the old column name exists but the new one is unique, 
        ///         then add a new column and move the relevant data over. Do not move any data that comes from other tables. 
        /// </summary>
        /// <param name="acr"></param>
        /// <param name="c"></param>
        internal override void Expand(AlterColumnRename acr, Command c)
        {
            // If the table doesn't match, punt
            if (!toMerge.Contains(acr.Table))
                return;
            // get all column that is not belong to acr.Table
            Dictionary<string, Dictionary<string, Domain.Domain>> tempTabs = new Dictionary<string,Dictionary<string,GuavaData.Domain.Domain>>(inputTables);
            tempTabs.Remove(acr.Table);
            Dictionary<string, Dictionary<string, Domain.Domain>>.ValueCollection valColl = tempTabs.Values;
            // check that the old column name is unique or not
            bool flags = true;
            foreach (Dictionary<string, Domain.Domain> valDic in valColl)
            {
                if (valDic.ContainsKey(acr.OldName))
                {
                    flags = false;
                    break;
                }
            }
            // flags = true means there is no duplicate column name.
            if (flags)
            {
                // case 1: the new name of the column exists
                if (mergedColDomains.ContainsKey(acr.NewName))
                {
                    RowConstant rc = new RowConstant(new string[] { acr.NewName }, new Domain.Domain[] { mergedColDomains[acr.NewName] }, new object[] { acr.OldName });
                    Update up = new Update(resName, new string[] { acr.NewName }, rc);
                    up.AddCondition(newColumn, Condition.NewCondition(ConditionType.EQ, acr.Table));
                    DropColumn dc = new DropColumn(resName, acr.OldName);
                    // update column in internal presentation
                    Dictionary<string, Domain.Domain> tempColL = new Dictionary<string,GuavaData.Domain.Domain>(inputTables[acr.OldName]);
                    tempColL.Add(acr.NewName, tempColL[acr.OldName]);
                    tempColL.Remove(acr.OldName);
                    inputTables[acr.OldName] = tempColL;
                    c.NotifyChange(new Command(up), new Command(dc));
                }
                else // case 2: the new column is unique
                {
                    // update column in internal presentation
                    mergedColDomains.Add(acr.NewName, mergedColDomains[acr.OldName]);
                    mergedColDomains.Remove(acr.OldName);
                    Dictionary<string, Domain.Domain> tempColL = new Dictionary<string,GuavaData.Domain.Domain>(inputTables[acr.OldName]);
                    tempColL.Add(acr.NewName, tempColL[acr.OldName]);
                    tempColL.Remove(acr.OldName);
                    inputTables[acr.OldName] = tempColL;
                    AlterColumnRename ac = new AlterColumnRename(resName, acr.OldName, acr.NewName);
                    c.NotifyChange(new Command(ac));
                }

            }
            else// the old column name is duplicate
            {
                RowConstant rc = new RowConstant(new string[] { acr.NewName, acr.OldName }, new Domain.Domain[] { mergedColDomains[acr.NewName], mergedColDomains[acr.OldName] }, new object[] { acr.OldName, null });
                Update up = new Update(resName, new string[] { acr.NewName, acr.OldName }, rc);
                up.AddCondition(newColumn, Condition.NewCondition(ConditionType.EQ, acr.Table));
                // case 3: the new name of the column exists
                if (mergedColDomains.ContainsKey(acr.NewName))
                {
                    // update column in internal presentation
                    Dictionary<string, Domain.Domain> tempColL = new Dictionary<string,GuavaData.Domain.Domain>(inputTables[acr.OldName]);
                    tempColL.Add(acr.NewName, tempColL[acr.OldName]);
                    tempColL.Remove(acr.OldName);
                    inputTables[acr.OldName] = tempColL;
                    c.NotifyChange(new Command(up));
                }
                else// case 4: the new name of the column is unique
                {
                    AddColumn adc = new AddColumn(resName, acr.NewName, mergedColDomains[acr.OldName]);
                    // update column in internal presentation
                    mergedColDomains.Add(acr.NewName, mergedColDomains[acr.OldName]);
                    mergedColDomains.Remove(acr.OldName);
                    Dictionary<string, Domain.Domain> tempColL = new Dictionary<string,GuavaData.Domain.Domain>(inputTables[acr.OldName]);
                    tempColL.Add(acr.NewName, tempColL[acr.OldName]);
                    tempColL.Remove(acr.OldName);
                    inputTables[acr.OldName] = tempColL;
                    c.NotifyChange(new Command(adc), new Command(up));
                }
            }

        }
        /// <summary>
        /// To rename the Element of the coulomn needs to check that there are all rename element functions  
        /// for every table that has the same column in the same transaction. If not throw an error.
        /// </summary>
        /// <param name="aer"></param>
        /// <param name="c"></param>
        internal override void Expand(AlterElementRename aer, Command c)
        {
            // If the table doesn't match, punt
            if (!toMerge.Contains(aer.Table))
                return;
            // Check that there is a reanme element operator of the same element, column and table before or not.
            // If yes, the operator have already operated.
            if (reList.Contains(aer))
            {
                c.NotifyChange();
            }
            else
            {
                Dictionary<string, Dictionary<string, Domain.Domain>> tempTabs = new Dictionary<string, Dictionary<string, Domain.Domain>>(inputTables);
                tempTabs.Remove(aer.Table);
                List<string> tbList = new List<string>();
                // find table  which has the same column as aer.Column 
                foreach (KeyValuePair<string, Dictionary<string, Domain.Domain>> p in tempTabs)
                {
                    Dictionary<string, Domain.Domain> colL = p.Value;
                    if (colL.ContainsKey(aer.Column))
                    {
                        tbList.Add(p.Key);

                    }
                }
                List<Command> comL = c.container.Contents;
                List<Operator> tempOp = new List<Operator>();// keep the operator that is valid from the transaction
                if (tbList.Count > 0)
                {
                    bool flags = false;
                    for (int i = 0; i < tbList.Count; i++)
                    {
                        flags = false;
                        foreach (Command a in comL)
                        {
                            if ((a.root is AlterElementRename) && 
                                ((a.root as AlterElementRename).Table.Equals(tbList[i])) &&
                                ((a.root as AlterElementRename).Column.Equals(aer.Column)) && 
                                ((a.root as AlterElementRename).OldName.Equals(aer.OldName)) && 
                                ((a.root as AlterElementRename).NewName.Equals(aer.NewName)))
                            {
                                tempOp.Add(a.root);
                                flags = true;
                                break;
                                
                            }     
                        }
                        if (!flags)
                        {
                            throw new Exception("The Old Element " + aer.OldName + " in the same Column " + aer.Column + " need to be changed.");
                        }
                    }
                    this.reList.AddRange(tempOp); // combine the temp operator list to the add element list in memory
                    //alter domain in the column
                    Domain.Enumerated e = mergedColDomains[aer.Column] as Domain.Enumerated;
                    Domain.Enumerated enew = new GuavaData.Domain.Enumerated(e.Items);
                    enew.Items.Remove(aer.OldName);
                    enew.Items.Add(aer.NewName);
                    mergedColDomains[aer.Column] = enew;
                    Dictionary<string, Domain.Domain> d = inputTables[aer.Table];
                    d[aer.Column] = enew;
                    if (tbList.Count > 0)// update domain of all table which have the same column as aer.column
                    {
                        for (int j = 0; j < tbList.Count; j++)
                        {
                            Dictionary<string, Domain.Domain> dt = inputTables[tbList[j]];
                            dt[aer.Column] = enew;
                        }
                    }
                    aer.Table = resName;
                }
            }
        }
        /// <summary>
        /// Delete the name of the table. To do this operator, we need to drop the table name from the Domain of the Column cout.
        /// We also need to check that there are any column in that table is unique then we need to drop that column from the 
        /// merged table too. 
        /// </summary>
        /// <param name="dt"></param>
        /// <param name="c"></param>
        internal override void Expand(DropTable dt, Command c)
        {
            // If the table doesn't match, punt
            if (!toMerge.Contains(dt.Table))
                return;
            DropElement dropTab = new DropElement(resName, newColumn, dt.Table);  
            Dictionary<string, Domain.Domain> colDic = inputTables[dt.Table];
            Dictionary<string, Domain.Domain>.KeyCollection keyColl = colDic.Keys;
            // get all column that is not belong to dt.Table
            Dictionary<string, Dictionary<string, Domain.Domain>> tempTabs = new Dictionary<string, Dictionary<string, Domain.Domain>>(inputTables);
            tempTabs.Remove(dt.Table);
            Dictionary<string, Dictionary<string, Domain.Domain>>.ValueCollection valColl = tempTabs.Values;
            // get all column of dt.Table that is unique
            bool flags = true;
            List<Command> drpList = new List<Command>();
            foreach (string keys in keyColl)
            {
                flags = true;
                // loop in all dictionary of each table
                foreach (Dictionary<string, Domain.Domain> valDic in valColl)
                {
                    if (valDic.ContainsKey(keys))
                    {
                        flags = false;
                        break;
                    }
                }
                
                if (flags)
                {
                    drpList.Add(new Command(new DropColumn(resName, keys)));
                    mergedColDomains.Remove(keys);// update internal respresent
                }
            }
            if (drpList.Count > 0)
            { 
                drpList.Add(new Command(dropTab));
                c.NotifyChange(drpList.ToArray());
            }
            else
            {
                c.NotifyChange(new Command(dropTab));
                inputTables.Remove(dt.Table);// update internal respresent
                toMerge.Remove(dt.Table);
            }
           
        }
        /// <summary>
        /// Drop the non-key column from the table. There are two cases.
        /// Case 1: If the column name is unique, then drop the column from the merged table.
        /// Case 2: If the column name is exists, then use an update statement to set the column values to null.
        /// </summary>
        /// <param name="dc"></param>
        /// <param name="c"></param>
        internal override void Expand(DropColumn dc, Command c)
        {
            // If the table doesn't match, punt
            if (!toMerge.Contains(dc.Table))
                return;
            // If the column is a key column, then throw an error.
            if(this.merKeys.Contains(dc.Column))
            {
                throw new Exception("The Column " + dc.Column + " is a key column. It cannot be dropped from the table.");
            }
            else
            {
                // get all column that is not belong to dc.Table
                Dictionary<string, Dictionary<string, Domain.Domain>> tempTabs = new Dictionary<string, Dictionary<string, Domain.Domain>>(inputTables);
                tempTabs.Remove(dc.Table);
                Dictionary<string, Dictionary<string, Domain.Domain>>.ValueCollection valColl = tempTabs.Values;
                // loop through all dictionary of each table to find that the coulmn is unique or not. 
                bool flags = true;
                foreach (Dictionary<string, Domain.Domain> valDic in valColl)
                {
                    if (valDic.ContainsKey(dc.Column))
                    {
                        flags = false;
                        break;
                    }
                }
                // flags is true means the column is unique.
                if (flags)// case 1: the coulmn is unique.
                {
                    DropColumn dr = new DropColumn(resName, dc.Column);
                    c.NotifyChange(new Command(dr));
                    mergedColDomains.Remove(dc.Column);// update internal respresent
                }
                else// case 2: the column exists.
                {
                    List<string> colList = new List<string>();
                    colList.Add(dc.Column);
                    Update up = new Update(resName, colList, null);
                    up.AddCondition(newColumn, Condition.NewCondition(ConditionType.EQ, dc.Table));
                    c.NotifyChange(new Command(up));
                    // update internal respresent
                    Dictionary<string, Domain.Domain> tempColL = inputTables[dc.Table];
                    tempColL.Remove(dc.Column);
                }
            }
        }
        /// <summary>
        /// To delete the Element from the coulomn needs to check that there are all drop element functions  
        /// for every table which has the same column in the same transaction. If not throw an error.
        /// </summary>
        /// <param name="de"></param>
        /// <param name="c"></param>
        internal override void Expand(DropElement de, Command c)
        {
            // If the table doesn't match, punt
            if (!toMerge.Contains(de.Table))
                return;
            // Check that there is a delete element operator of the same element and column before or not.
            // If yes, the operator have already operated.
            if (deList.Contains(de))
            {
                c.NotifyChange();
            }
            else
            {
                Dictionary<string, Dictionary<string, Domain.Domain>> tempTabs = new Dictionary<string, Dictionary<string, Domain.Domain>>(inputTables);
                tempTabs.Remove(de.Table);
                List<string> tbList = new List<string>();
                // find table which has the same column as de.Column
                foreach (KeyValuePair<string, Dictionary<string, Domain.Domain>> p in tempTabs)
                {
                    Dictionary<string, Domain.Domain> colL = p.Value;
                    if (colL.ContainsKey(de.Column))
                    {
                        tbList.Add(p.Key);

                    }
                }
                List<Command> comL = c.container.Contents;
                List<Operator> tempOp = new List<Operator>();
                if (tbList.Count > 0)
                {
                    for (int i = 0; i < tbList.Count; i++)
                    {
                        bool flags = false;
                        foreach (Command a in comL)
                        {
                            if ((a.root is AddElement) && ((a.root as DropElement).Table.Equals(tbList[i]))
                                && ((a.root as DropElement).Column.Equals(de.Column))
                                && ((a.root as DropElement).Element.Equals(de.Element)))
                            {
                                tempOp.Add(a.root);
                                flags = true;
                                break;
                                
                            }

                        }
                        if (!flags)
                        {
                            throw new Exception("The Element " + de.Element + " in the same Column " + de.Column + " need to be deleted.");
                        }
                    }
                    this.deList.AddRange(tempOp);// combine the temp operator list to the add element list in memory
                    //alter domain in the column
                    Domain.Enumerated e = mergedColDomains[de.Column] as Domain.Enumerated;
                    Domain.Enumerated enew = new GuavaData.Domain.Enumerated(e.Items);
                    enew.Items.Remove(de.Element);
                    mergedColDomains[de.Column] = enew;
                    Dictionary<string, Domain.Domain> d = inputTables[de.Table];
                    d[de.Column] = enew;
                    if (tbList.Count > 0)// update domain of all table which have the same column as ae.column
                    {
                        for (int j = 0; j < tbList.Count; j++)
                        {
                            Dictionary<string, Domain.Domain> dt = inputTables[tbList[j]];
                            dt[de.Column] = enew;
                        }
                    }
                    de.Table = resName;
                }
            }
        }
        /// <summary>
        /// Insert a new row into a specific table.
        /// </summary>
        /// <param name="i"></param>
        /// <param name="c"></param>
        internal override void Expand(Insert i, Command c)
        {
            // If the table doesn't match, punt
            if (!toMerge.Contains(i.Table))
                return;
            
            // Process the child
            i.Child.Visit(this, c);
            i.Columns.Add(newColumn);
             
            if (i.Child is RowConstant)
            {
                (i.Child as RowConstant).AddValue(newColumn, mergedColDomains[newColumn], i.Table);
                i.Table = this.resName;
            }
            else
            {
                RowConstant rc = new RowConstant(new string[] { newColumn }, new Domain.Domain[] { mergedColDomains[newColumn] }, new object[] { i.Table });
                CrossJoin cQuery = new CrossJoin(i.Child, rc);
                i.Table = this.resName;
                i.NotifyChange(i.Child, cQuery);
            }

        }
        /// <summary>
        /// Update rows in a table and the update columns are non-key columns. 
        /// </summary>
        /// <param name="u"></param>
        /// <param name="c"></param>
        internal override void Expand(Update u, Command c)
        {
            // If the table doesn't match, punt
            if (!toMerge.Contains(u.Table))
                return;
            
            List<string> upCol = u.Columns;
            bool flags = true;
            foreach (string col in u.Columns)
            {
                if (this.merKeys.Contains(col))
                {
                    flags = false;
                    break;
                }
            }
            //flags is true means the column list in the update parameter does not contain the key columns.
            if (flags)
            {
                List<object> colList = new List<object>();
                colList.Add(u.Table);//  name(T) is name of input table
                u.AddCondition(newColumn, Condition.NewCondition(ConditionType.EQ, colList));
                u.Table = this.resName;
            }
            else
                throw new Exception("The update columns include key columns. It cannot be updated.");

        }
        /// <summary>
        /// Delete rows that meet the condition from a table.
        /// </summary>
        /// <param name="d"></param>
        /// <param name="c"></param>
        internal override void Expand(Delete d, Command c)
        {
            // If the table doesn't match, punt
            if (!toMerge.Contains(d.Table))
                return;

            List<object> colList = new List<object>();
            colList.Add(d.Table);
            d.AddCondition(newColumn, Condition.NewCondition(ConditionType.EQ, colList));
            d.Table = this.resName;
        }
        /// <summary>
        /// Add a foregin key to a table. There are three cases.
        /// Case 1: The childTable and parentTable are in the list of merged table, 
        ///         then add a condition to both filter of childTable and parentTable.
        /// Case 2: The childTable is in the list of merged table but the parentTable is not, 
        ///         then add a condition to the filter of childTable.
        /// Case 3: The childTable is not in the list of merged table but parentTable is in the list, 
        ///         then add a condition to the filter of parentTable.
        /// </summary>
        /// <param name="fk"></param>
        /// <param name="c"></param>
        internal override void Expand(ForeignKey fk, Command c)
        {
            // If the table doesn't match, punt
            if (toMerge.Contains(fk.ChildTable))
            {
                if (toMerge.Contains(fk.ParentTable))
                {
                    fk.ChildConditionColumns.Add(newColumn);
                    fk.ChildConditions.Add(Condition.NewCondition(ConditionType.EQ, fk.ChildTable));
                    fk.ChildTable = resName;
                    fk.ParentConditionColumns.Add(newColumn);
                    fk.ParentConditions.Add(Condition.NewCondition(ConditionType.EQ, fk.ParentTable));
                    fk.ParentTable = resName;
                }
                else
                {
                    fk.ChildConditionColumns.Add(newColumn);
                    fk.ChildConditions.Add(Condition.NewCondition(ConditionType.EQ, fk.ChildTable));
                    fk.ChildTable = resName;
                }
            }
            else
            {
                if (toMerge.Contains(fk.ParentTable))
                {
                    
                    fk.ParentConditionColumns.Add(newColumn);
                    fk.ParentConditions.Add(Condition.NewCondition(ConditionType.EQ, fk.ParentTable));
                    fk.ParentTable = resName;
                    /*
                    fk.ParentColumns.Add(newColumn);
                    fk.ChildColumns.Add(newColumn + "_fk");
                    //how to actually add this column to the child table...? - This works.
                    AddColumn ac = new AddColumn(fk.ChildTable,newColumn + "_fk", new Domain.Enumerated());
                    Command c1 = new Command(ac);
                    Command c2 = new Command(fk);
                    c1.container = c.container;
                    c2.container = c.container;
                    c.NotifyChange(c1, c2);
                     * */
                }
            }

        }

        internal override void Expand(TableRef tr, Command c)
        {
            // If the table doesn't match, punt
            if (!toMerge.Contains(tr.Table))
                return;

            TableRef tref = new TableRef(this.resName, tr.Alias);

            foreach (KeyValuePair<string, Domain.Domain> p in this.mergedColDomains)
            {
                tref.AddColumn(p.Key, p.Key, p.Value);
            }
            Filter fil = new Filter(tref);
            fil.AddCondition(tr.Alias + "." + this.newColumn,
                Condition.NewCondition(ConditionType.EQ, tr.Table));
            

            Project proj = new Project(fil, tr.Columns);
            tr.parent.NotifyChange(tr, proj);
            
        }

        public override GuavaData.Transform Inverse()
        {
            HSplit hs = new HSplit(this.resName, newColumn);
            return hs;
        }

        public override List<XElement> ToXElement()
        {
            XElement x = new XElement("Transform", new XAttribute("type", "HMerge"), new XAttribute("mergedTable", resName),
                new XAttribute("mergedColumn", newColumn));
            for (int i = 0; i < toMerge.Count; i++)
                x.Add(new XElement(toMerge[i]));
            List<XElement> xList = new List<XElement>();
            if (!(this.Child is SqlProvider))
                xList = ((Transform)this.Child).ToXElement();
            xList.Add(x);
            return xList;
        }

        new public static HMerge FromXElement(XElement x)
        {
            string mergedTable = x.Attribute("mergedTable").Value;
            string mergeColumn = x.Attribute("mergedColumn").Value;
            List<string> tables = new List<string>();
            foreach (var tab in x.Elements())
                tables.Add(tab.Name.LocalName);
            if (tables.Count < 1)
                throw new Exception("While loading HMerge from XML: there were no tables to be merged");
            if (!(mergedTable == null) && !(mergeColumn == null))
                return new HMerge(tables, mergedTable, mergeColumn);
            else
                throw new Exception("While loading HMerge from XML: mergedTable or mergeColumn was null");
        }
    }
}
